// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: leon

#pragma once

#include <algorithm>
#include <functional>
#include <limits>
#include <map>
#include <string>
#include <vector>
#include <tuple>

#include "coin2/base/string_util.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/processor.h"
#include "coin2/exchange/huobi_futures/symbology/product.h"
#include "coin2/exchange/feed.pb.h"
#include "coin2/exchange/huobi/feed_v1/request_generator.h"

namespace coin2::exchange::huobi_futures::feed_v1 {
namespace impl {

using coin2::exchange::feed::WebsocketConfig;

  static int64_t GetPeriod(const std::string& channel) {
    if (channel.find("period=1min") != std::string::npos) {
      return (int64_t)60 * 1'000'000'000;
    } else if (channel.find("period=5min") != std::string::npos) {
      return (int64_t)5 * 60 * 1'000'000'000;
    } else if (channel.find("period=15min") != std::string::npos) {
      return (int64_t)15 * 60 * 1'000'000'000;
    } else if (channel.find("period=30min") != std::string::npos) {
      return (int64_t)30 * 60 * 1'000'000'000;
    } else if (channel.find("period=60min") != std::string::npos) {
      return (int64_t)60 * 60 * 1'000'000'000;
    } else if (channel.find("period=4hour") != std::string::npos) {
      return (int64_t)4 * 60 * 60 * 1'000'000'000;
    } else if (channel.find("period=1day") != std::string::npos) {
      return (int64_t)24 * 60 * 60 * 1'000'000'000;
    } else if (channel.find("period=1mon") != std::string::npos) {
      return (int64_t)28 * 24 * 60 * 60 * 1'000'000'000;
    } else {
      THROW() << "Not new channel: " << channel;
    }

    return std::numeric_limits<int64_t>::quiet_NaN();
  }

static void BuildKlineRequest(
    const std::vector<std::string>& channels,
    const std::vector<std::string>& symbols,
    const std::map<std::string, std::string>& channel_templates,
    const std::string& request_template,
    int64_t timestamp,
    const WebsocketConfig* config,
    std::map<std::tuple<std::string, std::string>,
    boost::posix_time::ptime>* last_long_query_time,
    std::vector<std::string>* rest_channels,
    std::vector<std::string>* reqs) {
  std::vector<std::string> ch = channels;
  std::vector<std::string> ch_kline;
  auto iter = ch.begin();
  while (iter != ch.end()) {
    if (StringStartsWith(*iter, "kline_rest")) {
      ch_kline.push_back(*iter);
      iter = ch.erase(iter);
    } else {
      iter++;
    }
  }

  if (!ch_kline.empty()) {
    boost::posix_time::ptime round_time =
        base::feed::RequestGenerator::TimestampRoundHour(timestamp);

    for (auto& ch_k : ch_kline) {
      std::string c = channel_templates.at(ch_k);
      auto interval_time = GetPeriod(c);
      for (auto& symbol : symbols) {
        std::vector<std::pair<int64_t, int64_t>> durations;
        bool long_query = base::feed::RequestGenerator::CheckLongQuery(
            round_time, symbol, ch_k, last_long_query_time);
        if (long_query) {
          durations = base::feed::RequestGenerator::GetDurations(
              config->kline_length(), config->kline_start(), config->kline_end(),
              interval_time, 1000);
        } else {
          // query small data
          durations = base::feed::RequestGenerator::GetDurations(
              "3", "", "",interval_time, 1000);
        }
        for (const auto& d : durations) {
          std::string c_copy = c;
          boost::replace_all(c_copy, "{}", symbol);
          boost::replace_all(c_copy, "{from}", std::to_string(d.first / 1'000'000'000L));
          boost::replace_all(c_copy, "{to}", std::to_string(d.second / 1'000'000'000L));
          std::string s = request_template;
          boost::replace_all(s, "{channel}", c_copy);
          reqs->emplace_back(s);
        }
      }
    }
  }

  *rest_channels = ch;
}

class HuobiFuturesRequestGenerator : public base::feed::RequestGenerator {
 public:
  explicit HuobiFuturesRequestGenerator(const WebsocketConfig* ws_config)
      : config_(ws_config) {}

  std::vector<std::string> GenerateRequests(
      const std::vector<std::string>& channels,
      const std::vector<std::string>& symbols,
      const std::map<std::string, std::string>& channel_templates,
      const std::string& request_template) override {
    std::vector<std::string> reqs;
    std::map<std::string, std::string> request_channel_templates;
    std::vector<std::string> request_channels;
    std::string request_template_adjust;
    if (request_template.find("{unsub}") != std::string::npos) {
      // unsubscription_req = '{{unsub}}'
      request_template_adjust = "{{channel}}";
      huobi::feed_v1::HuobiRequestGenerator::GetUnsubscribeTemplate(
          channels, channel_templates,
          &request_channels, &request_channel_templates);
    } else {
      request_template_adjust = request_template;
      request_channels = channels;
      request_channel_templates = channel_templates;
    }

    std::vector<std::string> ch = request_channels;
    std::string liquidation_channel = "liquidation_order";
    auto it = std::find(ch.begin(), ch.end(), liquidation_channel);
    if (it != ch.end()) {
      for (auto& symbol : symbols) {
        std::vector<std::string> splitted;
        splitted = Split(symbol, "_");
        CHECK_EQ(splitted.size(), 2);
        std::string c = request_channel_templates.at(liquidation_channel);
        boost::replace_all(c, "{}", splitted[0]);  // must specify base currency.
        std::string s = request_template_adjust;
        boost::replace_all(s, "{channel}", c);
        reqs.push_back(s);
      }

      ch.erase(it);
    }

    std::string open_interest = "open_interest";
    it = std::find(ch.begin(), ch.end(), open_interest);
    if (it != ch.end()) {
      for (auto& symbol : symbols) {
        std::string feed_code = ConvertSymbolToCode(symbol);
        std::string c = request_channel_templates.at(open_interest);
        boost::replace_all(c, "{}", feed_code);  // must specify base currency.
        std::string s = request_template_adjust;
        boost::replace_all(s, "{channel}", c);
        reqs.push_back(s);
      }

      ch.erase(it);
    }

    std::string index = "index";
    it = std::find(ch.begin(), ch.end(), index);
    if (it != ch.end()) {
      for (auto& symbol : symbols) {
        std::string feed_code = ConvertSymbolToIndexSymbol(symbol);
        std::string c = request_channel_templates.at(index);
        boost::replace_all(c, "{}", feed_code);  // must specify base currency.
        std::string s = request_template_adjust;
        boost::replace_all(s, "{channel}", c);
        reqs.push_back(s);
      }

      ch.erase(it);
    }

    std::vector<std::string> reqs_kline;
    std::vector<std::string> rest_channels;
    auto timestamp = MarketTimestampProvider::GetTimestamp();
    BuildKlineRequest(
      ch, symbols, request_channel_templates, request_template_adjust, timestamp, config_, 
      &last_long_query_time_, &rest_channels, &reqs_kline);
    reqs.insert(reqs.end(), reqs_kline.begin(), reqs_kline.end());

    std::vector<std::string> reqs_others = RequestGenerator::GenerateRequests(
        rest_channels, symbols, request_channel_templates, request_template_adjust);
    reqs.insert(reqs.end(), reqs_others.begin(), reqs_others.end());

    return reqs;
  }

 private:
  static std::string ConvertSymbolToCode(const std::string& symbol) {
    using coin2::exchange::huobi_futures::symbology::HuobiFuturesProduct;

    auto pi = HuobiFuturesProduct::FromNativeStr(
        std::string(symbol), GetCurrentTimestamp());
    std::stringstream ss;
    ss << pi.base().currency() << pi.expiry_str().substr(2);
    return ss.str();
  }

  static std::string ConvertSymbolToIndexSymbol(const std::string& symbol) {
    using coin2::exchange::huobi_futures::symbology::HuobiFuturesProduct;

    auto pi = HuobiFuturesProduct::FromNativeStr(
        std::string(symbol), GetCurrentTimestamp());
    return pi.base().currency();
  }

  const WebsocketConfig* config_;
};

class HuobiFuturesSwapRequestGenerator : public base::feed::RequestGenerator {
 public:
  explicit HuobiFuturesSwapRequestGenerator(const WebsocketConfig* ws_config)
      : config_(ws_config) {}

  std::vector<std::string> GenerateRequests(
      const std::vector<std::string>& channels,
      const std::vector<std::string>& symbols,
      const std::map<std::string, std::string>& channel_templates,
      const std::string& request_template) override {
    std::vector<std::string> reqs;
    std::map<std::string, std::string> request_channel_templates;
    std::vector<std::string> request_channels;
    std::string request_template_adjust;
    if (request_template.find("{unsub}") != std::string::npos) {
      // unsubscription_req = '{{unsub}}'
      request_template_adjust = "{{channel}}";
      huobi::feed_v1::HuobiRequestGenerator::GetUnsubscribeTemplate(
          channels, channel_templates,
          &request_channels, &request_channel_templates);
    } else {
      request_template_adjust = request_template;
      request_channels = channels;
      request_channel_templates = channel_templates;
    }

    std::vector<std::string> rest_channels;
    auto timestamp = MarketTimestampProvider::GetTimestamp();
    BuildKlineRequest(
      request_channels, symbols, request_channel_templates, request_template_adjust, timestamp, config_,
      &last_long_query_time_, &rest_channels, &reqs);

    std::vector<std::string> reqs_others = RequestGenerator::GenerateRequests(
        rest_channels, symbols, request_channel_templates, request_template_adjust);
    reqs.insert(reqs.end(), reqs_others.begin(), reqs_others.end());
    return reqs;
  }
 private:
  const WebsocketConfig* config_;
};

}  // namespace impl

using impl::HuobiFuturesRequestGenerator;
using impl::HuobiFuturesSwapRequestGenerator;

}  // namespace coin2::exchange::huobi_futures::feed_v1
